#include "cpu.h"

#include <string.h>
#include <stdio.h>

#include "misc_func.h"
#include "nes.h"
#include "disasm.h"
#include "cpu_opcode.h"
#include "cpu_instr.h"
#include "mapper0.h"
#include "mapper2.h"
#include "../global_config.h"

CCpu::I0xXX CCpu::m_ITable[0x100] = {
  &CCpu::I0x00, &CCpu::I0x01, &CCpu::I0x02, &CCpu::I0x03,
  &CCpu::I0x04, &CCpu::I0x05, &CCpu::I0x06, &CCpu::I0x07,
  &CCpu::I0x08, &CCpu::I0x09, &CCpu::I0x0A, &CCpu::I0x0B,
  &CCpu::I0x0C, &CCpu::I0x0D, &CCpu::I0x0E, &CCpu::I0x0F,
  &CCpu::I0x10, &CCpu::I0x11, &CCpu::I0x12, &CCpu::I0x13,
  &CCpu::I0x14, &CCpu::I0x15, &CCpu::I0x16, &CCpu::I0x17,
  &CCpu::I0x18, &CCpu::I0x19, &CCpu::I0x1A, &CCpu::I0x1B,
  &CCpu::I0x1C, &CCpu::I0x1D, &CCpu::I0x1E, &CCpu::I0x1F,
  &CCpu::I0x20, &CCpu::I0x21, &CCpu::I0x22, &CCpu::I0x23,
  &CCpu::I0x24, &CCpu::I0x25, &CCpu::I0x26, &CCpu::I0x27,
  &CCpu::I0x28, &CCpu::I0x29, &CCpu::I0x2A, &CCpu::I0x2B,
  &CCpu::I0x2C, &CCpu::I0x2D, &CCpu::I0x2E, &CCpu::I0x2F,
  &CCpu::I0x30, &CCpu::I0x31, &CCpu::I0x32, &CCpu::I0x33,
  &CCpu::I0x34, &CCpu::I0x35, &CCpu::I0x36, &CCpu::I0x37,
  &CCpu::I0x38, &CCpu::I0x39, &CCpu::I0x3A, &CCpu::I0x3B,
  &CCpu::I0x3C, &CCpu::I0x3D, &CCpu::I0x3E, &CCpu::I0x3F,
  &CCpu::I0x40, &CCpu::I0x41, &CCpu::I0x42, &CCpu::I0x43,
  &CCpu::I0x44, &CCpu::I0x45, &CCpu::I0x46, &CCpu::I0x47,
  &CCpu::I0x48, &CCpu::I0x49, &CCpu::I0x4A, &CCpu::I0x4B,
  &CCpu::I0x4C, &CCpu::I0x4D, &CCpu::I0x4E, &CCpu::I0x4F,
  &CCpu::I0x50, &CCpu::I0x51, &CCpu::I0x52, &CCpu::I0x53,
  &CCpu::I0x54, &CCpu::I0x55, &CCpu::I0x56, &CCpu::I0x57,
  &CCpu::I0x58, &CCpu::I0x59, &CCpu::I0x5A, &CCpu::I0x5B,
  &CCpu::I0x5C, &CCpu::I0x5D, &CCpu::I0x5E, &CCpu::I0x5F,
  &CCpu::I0x60, &CCpu::I0x61, &CCpu::I0x62, &CCpu::I0x63,
  &CCpu::I0x64, &CCpu::I0x65, &CCpu::I0x66, &CCpu::I0x67,
  &CCpu::I0x68, &CCpu::I0x69, &CCpu::I0x6A, &CCpu::I0x6B,
  &CCpu::I0x6C, &CCpu::I0x6D, &CCpu::I0x6E, &CCpu::I0x6F,
  &CCpu::I0x70, &CCpu::I0x71, &CCpu::I0x72, &CCpu::I0x73,
  &CCpu::I0x74, &CCpu::I0x75, &CCpu::I0x76, &CCpu::I0x77,
  &CCpu::I0x78, &CCpu::I0x79, &CCpu::I0x7A, &CCpu::I0x7B,
  &CCpu::I0x7C, &CCpu::I0x7D, &CCpu::I0x7E, &CCpu::I0x7F,
  &CCpu::I0x80, &CCpu::I0x81, &CCpu::I0x82, &CCpu::I0x83,
  &CCpu::I0x84, &CCpu::I0x85, &CCpu::I0x86, &CCpu::I0x87,
  &CCpu::I0x88, &CCpu::I0x89, &CCpu::I0x8A, &CCpu::I0x8B,
  &CCpu::I0x8C, &CCpu::I0x8D, &CCpu::I0x8E, &CCpu::I0x8F,
  &CCpu::I0x90, &CCpu::I0x91, &CCpu::I0x92, &CCpu::I0x93,
  &CCpu::I0x94, &CCpu::I0x95, &CCpu::I0x96, &CCpu::I0x97,
  &CCpu::I0x98, &CCpu::I0x99, &CCpu::I0x9A, &CCpu::I0x9B,
  &CCpu::I0x9C, &CCpu::I0x9D, &CCpu::I0x9E, &CCpu::I0x9F,
  &CCpu::I0xA0, &CCpu::I0xA1, &CCpu::I0xA2, &CCpu::I0xA3,
  &CCpu::I0xA4, &CCpu::I0xA5, &CCpu::I0xA6, &CCpu::I0xA7,
  &CCpu::I0xA8, &CCpu::I0xA9, &CCpu::I0xAA, &CCpu::I0xAB,
  &CCpu::I0xAC, &CCpu::I0xAD, &CCpu::I0xAE, &CCpu::I0xAF,
  &CCpu::I0xB0, &CCpu::I0xB1, &CCpu::I0xB2, &CCpu::I0xB3,
  &CCpu::I0xB4, &CCpu::I0xB5, &CCpu::I0xB6, &CCpu::I0xB7,
  &CCpu::I0xB8, &CCpu::I0xB9, &CCpu::I0xBA, &CCpu::I0xBB,
  &CCpu::I0xBC, &CCpu::I0xBD, &CCpu::I0xBE, &CCpu::I0xBF,
  &CCpu::I0xC0, &CCpu::I0xC1, &CCpu::I0xC2, &CCpu::I0xC3,
  &CCpu::I0xC4, &CCpu::I0xC5, &CCpu::I0xC6, &CCpu::I0xC7,
  &CCpu::I0xC8, &CCpu::I0xC9, &CCpu::I0xCA, &CCpu::I0xCB,
  &CCpu::I0xCC, &CCpu::I0xCD, &CCpu::I0xCE, &CCpu::I0xCF,
  &CCpu::I0xD0, &CCpu::I0xD1, &CCpu::I0xD2, &CCpu::I0xD3,
  &CCpu::I0xD4, &CCpu::I0xD5, &CCpu::I0xD6, &CCpu::I0xD7,
  &CCpu::I0xD8, &CCpu::I0xD9, &CCpu::I0xDA, &CCpu::I0xDB,
  &CCpu::I0xDC, &CCpu::I0xDD, &CCpu::I0xDE, &CCpu::I0xDF,
  &CCpu::I0xE0, &CCpu::I0xE1, &CCpu::I0xE2, &CCpu::I0xE3,
  &CCpu::I0xE4, &CCpu::I0xE5, &CCpu::I0xE6, &CCpu::I0xE7,
  &CCpu::I0xE8, &CCpu::I0xE9, &CCpu::I0xEA, &CCpu::I0xEB,
  &CCpu::I0xEC, &CCpu::I0xED, &CCpu::I0xEE, &CCpu::I0xEF,
  &CCpu::I0xF0, &CCpu::I0xF1, &CCpu::I0xF2, &CCpu::I0xF3,
  &CCpu::I0xF4, &CCpu::I0xF5, &CCpu::I0xF6, &CCpu::I0xF7,
  &CCpu::I0xF8, &CCpu::I0xF9, &CCpu::I0xFA, &CCpu::I0xFB,
  &CCpu::I0xFC, &CCpu::I0xFD, &CCpu::I0xFE, &CCpu::I0xFF,
};

CCpu::CCpu(CNes* nes)
{
  memset(this, 0, sizeof(*this));
  m_NES = nes;
  Init();
}

CCpu::~CCpu()
{
}

void
CCpu::Init()
{
  memset(CPU_MEM, 0, sizeof(CPU_MEM));
  ROM = 0;
  m_Mapper = 0;

  // CPU memory
  RAM = CPU_MEM;
  ExpansionArea = CPU_MEM + 0x4020;
  SRAM = CPU_MEM + 0x6000;

  m_Stack = &CPU_MEM[0x0100];
  m_ZNTable[0] = Z_FLAG;
  for (int i = 1; i < 256; i++) {
    m_ZNTable[i] = (i & 0x80) ? N_FLAG : 0;
  }
}

#define NEW_MAPPER(m) \
case m: \
  m_Mapper = new CMapper##m##(this); \
  break;
#define DEFAULT_MAPPER() \
  default: \
    m_Mapper = new CMapper(this);
void
CCpu::CreateMapper(int mapper)
{
  switch (mapper)
  {
    NEW_MAPPER(0);
    NEW_MAPPER(2);
    DEFAULT_MAPPER();
  }
}

void
CCpu::InitRom(int mapper, int bankcount)
{
  m_ROMBankCount = bankcount;
  ROM = new u8[bankcount*PRGPageSize];
  CreateMapper(mapper);
}

void
CCpu::CloseRom()
{
  SAFE_DELETE(m_Mapper);
  SAFE_DELETE(ROM);
}

void
CCpu::Reset()
{
  memset(&m_Register, 0, sizeof(m_Register));
  m_Register.PC = ReadWord(RESET_VECTOR);
}

void
CCpu::Stop()
{
  Init();
  Reset();
  CloseRom();
}

u8
CCpu::GetNextOpcode(u16 addr)
{
  m_ExtraCycle = 0;
  return ReadByte(addr);
}

u16 pc_;
int
CCpu::Execute(int cycles)
{
  int c = 0;
  while (cycles > 0) {

#if 0
    char instr[100] = { 0 };
    DisasmSingle(instr, m_Register.PC);
    printf("%s\t\t%s", instr, instr + 15);
#endif

    pc_ = m_Register.PC;

    u8 op_code= GetNextOpcode(m_Register.PC++);
    int c_tmp = (this->*(m_ITable[op_code]))();

    c_tmp += m_ExtraCycle;
    cycles -= c_tmp;
    c += c_tmp;
    m_TotalCycle += c_tmp;

#if 0
    if (strlen(instr + 15) < 8) {
      printf("\t\tA:%02X X:%02X Y:%02X S:%02X F:%02X\n", m_Register.A,
             m_Register.X, m_Register.Y, m_Register.S, m_Register.F);
    } else {
      printf("\tA:%02X X:%02X Y:%02X S:%02X F:%02X\n", m_Register.A,
             m_Register.X, m_Register.Y, m_Register.S, m_Register.F);
    }
#endif
  }
  return c;
}

u8*
CCpu::GetBuf(u16 addr)
{
  if (addr > 0x2000) {
    return 0;
  }
  return &RAM[addr&0x7FF];
}

u8
CCpu::ReadByte(u16 addr)
{
  if (addr < 0x2000) {
    return RAM[addr&0x7FF];
  } else if (addr >= 0x2000 && addr < 0x4000) {
    // registers
    addr %= 8;
    addr += 0x2000;
    return m_NES->ReadPort(addr);
  } else if (addr < 0x4018) {
    // registers
    return m_NES->ReadPort(addr);
  } else if (addr < 0x6000) {
    return ExpansionArea[addr - 0x6000];
  } else if (addr < 0x8000) {
    return SRAM[addr - 0x8000];
  }
  int bank = (addr-0x8000) >> 13;
  return PrgRom[bank][addr&0x1FFF];
}

u16
CCpu::ReadWord(u16 addr)
{
  u16 t = ReadByte(addr + 1);
  t <<= 8;
  return t | ReadByte(addr);
}

u16
CCpu::ReadWordZeroPage(u8 addr)
{
  u16 t = ReadByte(addr + 1);
  t <<= 8;
  return t | ReadByte(addr);
}

void
CCpu::Write(u16 addr, u8 data)
{
  if (addr < 0x2000) {
    RAM[addr&0x7FF] = data;
  } else if (addr >= 0x2000 && addr < 0x4000) {
    // registers
    addr %= 8;
    addr += 0x2000;
    m_NES->WritePort(addr, data);
  } else if (addr < 0x4018) {
    // registers
    m_NES->WritePort(addr, data);
  } else if (addr < 0x6000) {
    ExpansionArea[addr - 0x6000] = data;
  } else if (addr < 0x8000) {
    SRAM[addr - 0x6000] = data;
  } else {
    m_Mapper->Write(addr, data);
  }
}

void
CCpu::NMIReq()
{
  NMI();
}
